package eerbaar.shared.internal.persist.save;

import java.util.Iterator;
import java.util.Stack;

import eerbaar.shared.persist.IPersistService;
import eerbaar.shared.persist.ISaver;

public class SaveableIterator implements Iterator<SaveableIterator.Result> {

    public static class Data {

        Stack<Momento> dataStack;

        Momento momento;

        ISaver<?> node;

        @Override
        public String toString() {

            String s = "";
            s += "data(\n";
            s += "  result=" + this.node + "\n";
            s += "  momento=" + this.momento + "\n";
            s += "  stack=" + this.dataStack + "\n";
            s += ")";

            return s;
        }
    }

    public class Result {

        public final int depth;

        public final ISaver<?> parent;

        public final ISaver<?> saveable;

        public Result(ISaver<?> saveable, int depth, ISaver<?> parent) {
            this.saveable = saveable;
            this.depth = depth;
            this.parent = parent;
        }

        @Override
        public String toString() {

            String s = "";
            s += "result(\n";
            s += "  depth=" + this.depth + "\n";
            s += "  saveable=" + this.saveable + "\n";
            s += "  parent=" + this.parent + "\n";
            s += ")";

            return s;
        }

    }

    private final IPersistService service;

    private final Stack<Momento> stack = new Stack<Momento>();

    public SaveableIterator(IPersistService service, ISaver<?> saveable) {

        Momento momento = new Momento(saveable);
        this.stack.add(momento);
        this.service = service;
    }

    private boolean checkForChildren(Data data) {

        boolean result = false;

        final ISaver<?> currentSavable = data.momento.current();
        ISaver<?> savable = this.service.getLeastDerivedClass(currentSavable);
        if (savable == null) {
            System.out.println("savable not found for "
                    + currentSavable.getClass());
        }
        assert (savable != null);

        ISaver<?>[] children = savable.getSavableChildren();
        if (children == null) {
            throw new RuntimeException("" + savable.getClass()
                    + " return null for getSavableChildren");
        }

        if (children.length > 0) {

            data.momento = new Momento(children);
            data.dataStack.push(data.momento);
            result = true;
        }

        return result;
    }

    private Stack<Momento> copyMomentoStack(final Stack<Momento> inStack) {

        Stack<Momento> workingStack = new Stack<Momento>();
        for (Momento m : inStack) {
            Momento copy = new Momento(m);
            workingStack.add(copy);
        }

        return workingStack;
    }

    @Override
    public boolean hasNext() {

        Stack<Momento> workingStack = copyMomentoStack(this.stack);
        Result test = next(workingStack);
        return (test != null);
    }

    @Override
    public Result next() {

        return next(this.stack);
    }

    private Result next(Stack<Momento> momentoStack) {

        Result result = null;
        Data data = new Data();
        data.dataStack = momentoStack;

        for (;;) {

            if (momentoStack.size() == 0) {
                break;
            }

            // ------------------
            // get current
            // ------------------

            data.momento = momentoStack.peek();
            data.node = data.momento.current();
            ISaver<?> parent = null;
            if (momentoStack.size() > 1) {
                Momento parentMomento = momentoStack
                        .get(momentoStack.size() - 2);
                parent = parentMomento.current();
            }
            int depth = momentoStack.size();
            result = new Result(data.node, depth, parent);

            // ------------------
            // setup next
            // ------------------

            if (checkForChildren(data)) {
                break;
            }

            data.momento.next();

            // ------------------
            // backup the stack
            // ------------------

            for (;;) {

                if (data.momento.isAtEnd()) {
                    data.dataStack.pop();
                    if (data.dataStack.size() == 0) {
                        data.momento = null;
                        break;
                    }
                    data.momento = data.dataStack.peek();
                    data.momento.next();
                }

                if (data.momento.isAtEnd()) {
                    continue;
                }

                break;
            }

            break;
        }

        return result;
    }

    @Override
    public void remove() {
        //
    }

    @Override
    public String toString() {

        return "" + this.stack;
    }

}
